﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WebServer;
using WebServer.Handlers;
using System.Threading;
using System.IO;
using System.Windows.Forms;

namespace RPGMapRevealer
{
    class MapServer
    {
        private HttpServer server;

        private MapInfo mapInfo;
        public MapInfo MapInfo
        {
            get { return mapInfo; }
            set
            {
                lock (synclock)
                {
                    if (mapInfo != value)
                    {
                        if (mapInfo != null)
                        {
                            mapInfo.MapUpdated -= mapInfo_MapUpdated;
                        }
                        mapInfo = value;
                        if (mapInfo != null)
                        {
                            mapInfo.MapUpdated += mapInfo_MapUpdated;
                            TileFolder = mapInfo.TileFolder;
                        }
                        SendKeyFrame();
                    }
                }
            }
        }

        private void mapInfo_MapUpdated(object sender, MapUpdatedEventArgs e)
        {
            lock (synclock)
            {
                if (mapInfo == null || e.Map != mapInfo)
                { return; }
                HistoryUpdateInfo update = new HistoryUpdateInfo();
                foreach (Tile tile in mapInfo.Tiles)
                {
                    int state = SummarizeState(tile);
                    int oldState;
                    if (!(tileState.TryGetValue(tile, out oldState)) || state != oldState)
                    {
                        tileState[tile] = state;
                        update.Tiles.Add(tile);
                    }
                }
                updateHistory.Add(update);
                Monitor.PulseAll(synclock);
            }
        }

        [RestFunction]
        private List<String> PrefetchImages()
        {
            List<String> files = new List<string>();
            foreach (FileInfo file in mapInfo.TileFolder.GetFiles())
            {
                files.Add(file.Name);
            }
            return files;
        }

        [RestFunction]
        private JsonMapView CurrentState()
        {
            if (mapInfo == null) { return null; }
            Tile player1 = mapInfo.PositionOfPlayer(1);
            if (player1 == null) { return null; }
            JsonMapView result = new JsonMapView()
            {
                UpdateNumber = currentUpdate,
                CenterX = player1.X,
                CenterY = player1.Y,
                TileWidth = mapInfo.GridConfig.TileWidth,
                TileHeight = mapInfo.GridConfig.TileHeight
            };
            return result;
        }

        [RestFunction]
        private JsonMapView WaitForUpdates(int lastUpdate)
        {
            lock (synclock)
            {
                DateTime timeout = DateTime.Now.AddSeconds(30);
                while (DateTime.Now < timeout)
                {
                    if (lastUpdate < currentUpdate)
                    {
                        JsonMapView result = CurrentState();
                        if (result != null)
                        {
                            result.TileUpdates = Tiles(lastUpdate);
                        }
                        return result;
                    }
                    else if (lastUpdate > currentUpdate)
                    {
                        return WaitForUpdates(0);
                    }
                    else
                    {
                        try { Monitor.Wait(synclock, timeout - DateTime.Now); }
                        catch { }
                    }
                }
                return null;
            }
        }

        [RestFunction]
        private IList<JSONTile> Tiles(int lastUpdate = 0)
        {
            lock (synclock)
            {
                if (lastUpdate < currentUpdate)
                {
                    List<JSONTile> result = new List<JSONTile>();
                    HashSet<Tile> updatedTiles = new HashSet<Tile>((from HistoryUpdateInfo hui in updateHistory
                                                                    where hui.updateNumber >= lastUpdate
                                                                    select hui.Tiles).SelectMany(x => x));
                    var r = from Tile tile in updatedTiles
                            select new JSONTile() { X = tile.X, Y = tile.Y, Img = (tile.EffectiveLightLevel > 0 ? tile.ImageName : String.Empty), Light = Math.Min(tile.EffectiveLightLevel, 10) };
                    return r.ToList();
                }
                else
                {
                    return new JSONTile[] { };
                }
            }
        }

        private void SendKeyFrame()
        {
            lock (synclock)
            {
                if (mapInfo == null)
                { return; }
                tileState.Clear();
                updateHistory.Clear();
                mapInfo_MapUpdated(null, new MapUpdatedEventArgs() { Map = mapInfo });
            }
        }

        private Dictionary<Tile, int> tileState = new Dictionary<Tile, int>();
        private List<HistoryUpdateInfo> updateHistory = new List<HistoryUpdateInfo>();

        //This is auto incremented.  There is no guard condition because it would take hundreds of thousands of years of clicking like a madman to make this overflow
        private static long currentUpdate = 0;

        private class HistoryUpdateInfo
        {
            public long updateNumber = currentUpdate++;
            public List<Tile> Tiles = new List<Tile>();
        }

        private int SummarizeState(Tile tile)
        {
            int HashCode =
            tile.X * 200 ^
            tile.Y * 1000 ^
            (tile.EffectiveLightLevel > 0 ? tile.ImageName : String.Empty).GetHashCode() ^
            Math.Min(tile.EffectiveLightLevel, 10) * 5000 ^
            (tile.IsRevealed ? 1 : 2);
            return HashCode;
        }

        private Object synclock = new object();
        private bool isRunning = false;
        private bool shouldBeRunning = false;

        private int port = 8123;
        public int Port
        {
            get { return port; }
            set
            {
                lock (synclock)
                {
                    if (port != value)
                    {
                        bool restart = shouldBeRunning;
                        Stop();
                        port = value;
                        server = null;
                        if (restart)
                        {
                            Start();
                        }
                    }
                }
            }
        }

        private DirectoryInfo tileFolder;
        private DirectoryInfo TileFolder
        {
            get { return tileFolder; }
            set
            {
                lock (synclock)
                {
                    if (tileFolder != value)
                    {
                        bool restart = shouldBeRunning;
                        Stop();
                        tileFolder = value;
                        server = null;
                        if (restart)
                        {
                            Start();
                        }
                    }
                }
            }
        }

        public void Stop()
        {
            lock (synclock)
            {
                if (isRunning)
                {
                    shouldBeRunning = false;
                    server.Stop();
                    isRunning = false;
                }
            }
        }

        public void Start()
        {
            lock (synclock)
            {
                if (!isRunning)
                {
                    shouldBeRunning = true;
                    if (!CanStartServer())
                    { return; }
                    if (server == null)
                    {
                        server = new HttpServer(port);
                        initServer();
                    }
                    server.Start();
                    isRunning = true;
                }
            }
        }

        private bool CanStartServer()
        {
            return tileFolder != null;
        }

        private void initServer()
        {
            String clientPath = Path.Combine(Application.StartupPath, "www");
            DirectoryInfo clientFolder = new DirectoryInfo(clientPath);

            server.AddDefaultHandler(new FilesHandler(clientFolder));
            if (!TileFolder.Exists)
            { TileFolder.Create(); }
            server.AddHandler("tiles", new FilesHandler(TileFolder, "tiles"));
            server.AddHandler("map", new RESTHandler(this));
        }
    }
}
